<!DOCTYPE html>
<meta charset="utf-8">
<title>Declarative Shadow DOM</title>
<link rel="author" href="mailto:masonf@chromium.org">
<link rel="help" href="https://github.com/whatwg/dom/issues/831">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src='../../html/resources/common.js'></script>

<body>
<style>
  * { white-space: pre; }
  iframe { display:none; }
</style>
<div id=log></div>

<div id=mainpage style="display:none">
  <div class=wrapper>
    <div class=host>
      <template shadowrootmode=open>
        <span class=content>Content</span>
      </template>
    </div>
  </div>
</div>

<script>
const content = `
  <html><body>
  <div class=wrapper>
    <div class=host>
      <template shadowrootmode=open>
        <span class=content>Content</span>
      </template>
    </div>
  </div>
  </body></html>
`;

function assert_dsd(el,shouldHaveShadow) {
  const wrapper = el.querySelector('.wrapper');
  assert_true(!!wrapper,'Unable to find wrapper element');
  const host = wrapper.querySelector('.host');
  assert_true(!!host,'Unable to find host element');
  if (shouldHaveShadow) {
    assert_true(!!host.shadowRoot, 'Shadow root NOT FOUND.');
    assert_true(!!host.shadowRoot.querySelector('.content'),'Unable to locate content');
  } else {
    assert_true(!host.shadowRoot, 'Shadow root FOUND - none should be present.');
    const tmpl = host.querySelector('template');
    assert_true(!!tmpl, 'The template should be left as a <template> element');
    assert_equals(tmpl.getAttribute('shadowrootmode'),'open','The shadowrootmode attribute should still be present');
    assert_true(!!tmpl.content.querySelector('.content'),'Unable to locate content');
  }
}

test(() => {
  const div = document.getElementById('mainpage');
  assert_dsd(div,true);
}, 'Non-fragment parsing needs no opt-in');

const noChildElements = ['iframe','noscript','script','select','style','template','textarea','title','colgroup'];
const elements = HTML5_ELEMENTS.filter(el => !noChildElements.includes(el));
for (let elementName of elements) {
  var t = test(function() {
    const el1 = document.createElement(elementName);
    el1.innerHTML = content;
    assert_dsd(el1,false);

    const templateContent = `<template id=tmpl>${content}</template>`;
    const el2 = document.createElement('div');
    el2.innerHTML = templateContent;
    assert_dsd(el2.querySelector('#tmpl').content,false);
  }, `innerHTML on a <${elementName}>`);
}

test(() => {
  const temp = document.createElement('template');
  temp.innerHTML = content;
  assert_dsd(temp.content,false, 'innerHTML should not allow declarative shadow content');
}, 'innerHTML on template');

test(() => {
  const templateContent = `<template id=tmpl>${content}</template>`;
  const temp = document.createElement('template');
  temp.innerHTML = templateContent;
  assert_dsd(temp.content.querySelector('#tmpl').content,false);
}, 'innerHTML on template, with nested template content');

test(() => {
  const div = document.createElement('div');
  const shadow = div.attachShadow({mode: 'open'});
  shadow.innerHTML = content;
  assert_dsd(shadow,false);
}, 'innerHTML on shadowRoot');

test(() => {
  const parser = new DOMParser();
  let fragment = parser.parseFromString(content, 'text/html');
  assert_dsd(fragment.body,false);
  fragment = parser.parseFromString(content, 'text/html', {includeShadowRoots: false});
  assert_dsd(fragment.body,false);
  fragment = parser.parseFromString(content, 'text/html', {includeShadowRoots: true});
  assert_dsd(fragment.body,false);
}, 'DOMParser (includeShadowRoots is historical)');

test(() => {
  const doc = document.implementation.createHTMLDocument('');
  doc.body.innerHTML = content;
  assert_dsd(doc.body,false);
}, 'createHTMLDocument with innerHTML - not supported');

test(() => {
  const doc = document.implementation.createHTMLDocument('');
  let range = doc.createRange();
  range.selectNode(doc.body);
  let documentFragment = range.createContextualFragment(content);
  assert_dsd(documentFragment,false);
}, 'createContextualFragment - not supported');

async_test((t) => {
  let client = new XMLHttpRequest();
  client.addEventListener('load', t.step_func_done(() => {
    assert_true(client.status == 200 && client.responseXML != null);
    assert_dsd(client.responseXML.body,false);
    t.done();
  }));
  client.open("GET", `data:text/html,${content}`);
  client.responseType = 'document';
  client.send();
}, 'XMLHttpRequest - not supported');

test(() => {
  const div = document.createElement('div');
  div.insertAdjacentHTML('afterbegin',content);
  assert_dsd(div,false);
}, 'insertAdjacentHTML on element - not supported');

test(() => {
  const id = 'doc-write-1';
  document.write(`<div id=${id} style="display:none">${content}</div>`);
  assert_dsd(document.getElementById(id),true);
}, 'document.write allowed from synchronous script loaded from main document');

test(() => {
  const id = 'doc-write-2';
  const doc = document.implementation.createHTMLDocument('');
  doc.write(`<div id=${id}>${content}</div>`);
  assert_dsd(doc.getElementById(id),false);
}, 'document.write disallowed on fresh document');


async_test((t) => {
  const iframe = document.createElement('iframe');
  iframe.style.display = "none";
  iframe.sandbox = "allow-same-origin";
  document.body.appendChild(iframe);
  iframe.addEventListener('load', t.step_func_done(() => {
    assert_dsd(iframe.contentDocument.body,true);
    t.done();
  }));
  iframe.srcdoc = content;
}, 'iframe');

async_test((t) => {
  const iframe = document.createElement('iframe');
  iframe.style.display = "none";
  document.body.appendChild(iframe);
  iframe.addEventListener('load', t.step_func_done(() => {
    assert_dsd(iframe.contentDocument.body,true);
    t.done();
  }));
  iframe.srcdoc = content;
}, 'iframe, no sandbox');

function getHandler(t, name, shouldHaveShadow) {
  return (e) => {
    t.step(() => {
      if (e.data.name == name) {
        assert_false(e.data.error,e.data.msg);
        assert_true(e.data.hasShadow == shouldHaveShadow);
        t.done();
      }
    });
  };
}
async_test((t) => {
  window.addEventListener('message', getHandler(t, 'iframe-sandbox', true));
}, 'sandboxed iframe allows declarative Shadow DOM');

async_test((t) => {
  window.addEventListener('message', getHandler(t,'iframe-no-sandbox', true));
}, 'iframe with no sandbox allows declarative Shadow DOM');

</script>

<iframe name="iframe-sandbox" sandbox="allow-scripts" src="support/declarative-child-frame.html" ></iframe>
<iframe name="iframe-no-sandbox" src="support/declarative-child-frame.html"></iframe>
